查看原文
其他

断其粮道——内核级拒绝服务攻击

minorory 看雪学院 2021-03-06
本文为看雪论坛优秀文章

看雪论坛作者ID:minorory





1. 何为断其粮道?


古时的军队大体分为两部分,一是前线作战的士兵,二是后方运粮的人员,两者共同组成一个系统,双方配合才能完成作战任务。

两者之间保持互相通信,前者就可以派人向后者催粮,后者就可以派人向前者运粮,而这个运粮(通信)的关键就在于粮道,如果被敌军切断,那么前线士兵就无法向后勤人员征求粮食(请求服务),后勤人员也就无法为前线士兵运送粮食(提供服务),此可谓之拒绝服务攻击。
 
以图示之,前线5万大军因吃不上饭而开始出现军心不稳的情况了……





2. 应用程序的粮道在哪里?


那么程序有类似的“粮道”吗,有的话在哪里呢?答案就要从3环进0环说起了。

Windows系统下有几个重要的DLL:Kernel32.dll、User32.dll、Gdi32.dll、Ntdll.dll。运行在3环的应用程序不可避免的会使用这些Dll提供的API,而这些API大部分会通过Ntdll.dll进入0环,参考《加密与解密》的第7章的图7.6。

 
举一个例子来说明这个过程吧!用Windbg和Win7 x86双机调试。

Kernel32.dll中的ReadProcessMemory,其代码如下:

 
其中只有一条jmp指令,跳到一个导入函数里去执行,我们看看该函数是什
么:

 
原来是kernelbase.dll中的同名函数,继续反汇编,在+12附近的位置又调用了一个导入函数:

 
继续看该导入函数是什么,原来是ntdll.dll中的NtReadVirtualMemory函数:

 
此时就到了关键的地方了。ntdll.dll中的NtReadVirtualMemory函数非常简单,就4句,非常简单,它的作用是什么呢?

在ntdll.dll中,大部分API也是这样的格式:

mov eax, API索引号mov edx, 一个地址 ;该地址里的值是一个函数地址call [edx] ;调用这个函数,实现进入0环retn 14h

API索引号就是在3环调用了一个API,这个API在内核也就是0环,会有对应的内核函数,这个内核函数的地址在SSDT表中位于某个位置,这个位置就是索引号。进0环后,得找到对应的内核函数进行调用吧,就是根据这个索引号找到的。
 
一个地址,里面存的值是一个函数地址,反汇编看一下就知道了

 
原来这个函数名为KiFastSystemCall,如果CPU支持快速调用的话,那么call [edx]就是call KiFastSystemCall,如果是通过中断门进入0环的话,那么call [edx]就是call KiIntSystemCall。
 
KiFastSystemCall里的sysenter指令实现进入0环。操作系统在执行这条指令前会把进0环后需要的东西(CS/SS/ESP/EIP)提前准备好,存入一个叫做MSR的寄存器中,等到sysenter指令执行时,就会从MSR寄存器中取出这些值赋给相应的寄存器,其中eip寄存器就得到一个值,这个值就是nt!KiFastCallEntry函数的地址。

 
那其实就明白了,通过快速调用进0环后的第一个要执行的函数就是KiFastCallEntry。这个函数的作用就是拿着前面传下来的API索引号,获取相应的内核函数,调用之,从而实现应用层想要的功能。整个过程整理如下:

 
把其他API的调用过程也画上去,看看整体的流程:

 
其中的“粮道”我把它选为了Ntdll.dll中的KiFastSystemCall,即上图蓝色的线,因为这个函数实现进入0环,3环的API就得通过这个必经之路进入0环。

如果把这个必经之路给断了,那岂不是3环API进不了0环,从而0环无法为3环提供服务,这就达到了内核级拒绝服务攻击。

还有一个地方也可以选为“粮道”,那就是MSR寄存器,进0环后要执行的函数地址是来源于MSR寄存器中的eip值,所以修改MSR寄存器也可以达到截断“粮道”的目的。





3. 截断应用程序的粮道


根据第2节可知,3环进0环的关键函数是Ntdll.dll中的KiFastSystemCall,而且是以下面的形式来对它进行调用的:

mov edx, 7ffe0300call [edx] ; call KiFastSystemCall

7ffe0300地址里保存了KiFastSystemCall的地址。

其实这里涉及一个名为_KUSER_SHARED_DATA的数据结构,在3环和0环分别定义了一个该结构,其中在3环,这个结构地址固定为7ffe0000;在0环,这个结构地址固定为ffdf0000。

这两个结构的内容完全相同,它们偏移+300处的成员名为SystemCall,保存的正好就是KiFastSystemCall函数的地址,所以上面的7ffe0300就是这么来的,看图:

 
好,既然3环API进0环,要访问7ffe0000+300处的成员,来实现进0环,那么我们把这个成员给改了(改之前拍个快照):

 ed 7ffe0000+300 12345678


把7ffe0000+300处的成员,随便改成12345678,那么3环进0环时,就会去12345678地址里去执行,而这个地址肯定是无意义的,看看会发生什么情况,根据我的实验得到:

Windbg首先有动静,问我中断还是忽略:



选b的话,Windbg会再次询问我相同的问题,再次选b,就会中断了:



此时的栈回溯信息如下:

 
如果选i的话,步骤和结果如下:

 
系统跑起来了,问题是,鼠标移到记事本里,会改变形状为插入型,移动到桌面会显示正常的光标,移动到开始菜单处会显示漏洞状,但是均无法点击。向虚拟机发送Ctrl+Alt+Delete键,也是没有反应的。

整个系统就保持这种状态,既不会蓝屏,Windbg也不会继续捕获到异常。



修改ffdf0000+300处的成员也会使系统出现这种现象。以下是修改ffdf0000+300中断后的栈回溯信息。





4. 实验结果说明


根据以上的的实验可知,修改进0环的关键位置,使得整个系统发生了无响应而不蓝屏的现象,大概可以解释成:是3环进0环的地方出了问题,而这个进0环的函数又是属于3环的,所以问题发生在3环而不是0环,故系统内核没有崩溃。
 
7ffe0000+300、ffdf0000+300这两个地方用来耍流氓是挺合适的,发现你对我的程序有分析行为,那我就阻断你进0环的路,你的程序得不到内核的服务,于是什么分析行为都无法进行下去了,这个就是断其粮道的含义。其实对一些函数设置钩子,使其他调用者得不到正确的结果,也可以叫做拒绝服务攻击。
 
那么怎么对付这个流氓行为呢?从前面的两个栈回溯信息就可以看出端倪。
 
如果反过来呢?我们在分析的时候,触发了检测代码,从而发生了这种系统无响应而不蓝屏的现象,于是就没有蓝屏文件为我们提供信息,而如果又不知道3环进0环的原理,那么即便是看Windbg的栈回溯信息,也没法对付,所以很可能就想不到是程序的“粮道”出了问题。
 
代码就不写了,有兴趣的同学可以自己写个驱动,随便检测一个进程,然后修改这两个7ffe0000+300、ffdf0000+300试试,看看有什么效果。
 
一点思考:系统无响应而不蓝屏,说明用户层废了,而内核还活着,此时我们提前写个驱动打算一直循环干活,那么在系统无响应期间,驱动是否也在工作呢?

如果在工作的话,那其他规范化的程序是不是就不能收集我们的种种行为呢?(应用层软件显然无法收集我们的行为了,内核层的驱动一般要和应用层通信,此时无法通信了,功能肯定大打折扣。)





5. 参考资料


《加密与解密》
《软件调试》




- End -


看雪ID:minorory

https://bbs.pediy.com/user-705350.htm

  *本文由看雪论坛 minorory 原创,转载请注明来自看雪社区。


推荐文章++++

* CVE-2017-0263 win32k漏洞分析笔记

* 0基础也能看懂的函数栈结构分析

* CVE-2020-1054分析

* 对比总结32/64位下Windows部分数据结构的异同

* CVE-2020-1350分析与复现

好书推荐












公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com


求分享

求点赞

求在看


“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存